﻿/*!
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Net.WebSockets;

namespace Pittypat.Web
{
    /// <summary>
    /// 向 WebSocket 请求客户端汇报操作进度。
    /// </summary>
    sealed class WebSocketAsyncProgress : IAsyncProgress
    {
        private float lastPercent;
        private WebSocket socket;
        private CancellationToken token;

        /// <summary>
        /// 使用目标 WebSocket 初始化 <see cref="WebSocketAsyncProgress"/> 的新实例。
        /// </summary>
        /// <param name="socket">目标 WebSocket。</param>
        /// <param name="token">用于取消异步操作的标记。</param>
        internal WebSocketAsyncProgress(WebSocket socket, CancellationToken token)
        {
            this.lastPercent = 0f;
            this.socket = socket;
            this.token = token;
        }

        internal float LastPercent
        {
            get
            {
                return this.lastPercent;
            }
        }

        /// <summary>
        /// 获取当前 WebSocket 对象。
        /// </summary>
        public WebSocket Socket
        {
            get
            {
                return this.socket;
            }
        }

        /// <summary>
        /// 获取用于取消异步操作的标记。
        /// </summary>
        public CancellationToken Token
        {
            get
            {
                return this.token;
            }
        }
        
        /// <summary>
        /// 汇报异步操作的进度。
        /// </summary>
        /// <param name="percent">当前进度的百分比。</param>
        /// <param name="message">当前进度下的消息。</param>
        /// <param name="value">同时发送给远程终端的值。</param>
        /// <returns>异步任务。</returns>
        public Task ReportAsync(float percent, string message, IJson value)
        {
            if (this.socket == null)
            {
                throw new ObjectDisposedException("对象已经被释放。");
            }

            if (percent > 100f)
            {
                percent = 100f;
            }

            var task = SendAsync(Message.Create(percent, message, value));
            this.lastPercent = percent;

            return task;
        }

        /// <summary>
        /// 从 WebSocket 中接收数据，直至 EndOfMessage 为 true 或连接被关闭。
        /// </summary>
        /// <param name="buffer">接收数据的缓冲区。</param>
        /// <param name="offset">缓冲区中的起始位置。</param>
        /// <param name="count">缓冲区的尺寸。</param>
        /// <returns>总共接收到的字节数。</returns>
        /// <exception cref="IndexOutOfRangeException">buffer 空间不足。</exception>
        internal async Task<int> ReceiveAsync(byte[] buffer, int offset = 0, int count = -1)
        {
            if (this.socket == null)
            {
                throw new ObjectDisposedException("对象已经被释放。");
            }

            if (buffer == null)
            {
                throw new ArgumentNullException("buffer");
            }

            offset = offset < 0 ? 0 : offset;
            count = count <= 0 ? buffer.Length - offset : count;

            if (offset >= buffer.Length)
            {
                throw new ArgumentOutOfRangeException("offset", "offset 已经超过了数组的最大长度。");
            }

            if (count > buffer.Length - offset)
            {
                throw new ArgumentOutOfRangeException("count", "count 已经超过了数组的最大剩余空间的长度。");
            }

            // 已经累计接收的字节数
            int total = 0;
            WebSocketReceiveResult result = null;

            do
            {
                if (this.socket.State != WebSocketState.Open)
                {
                    // 连接已经被关闭
                    this.socket.Abort();
                    return -1;
                }

                if (total >= count)
                {
                    // 已经没有剩余空间用于接收新数据
                    return total;
                }

                result = await this.socket.ReceiveAsync(new ArraySegment<byte>(buffer, offset, count - total), this.token);

                if (result.CloseStatus.HasValue)
                {
                    // 连接已经被关闭
                    this.socket.Abort();
                    return -1;
                }

                total += result.Count;
                offset += result.Count;

                if (result.EndOfMessage)
                {
                    break;
                }
            } while (true);

            return total;
        }

        /// <summary>
        /// 通过当前 WebSocket 发送指定的消息。
        /// </summary>
        /// <param name="msg">要发送的消息。</param>
        /// <param name="bufferSize">发送缓冲区的尺寸。</param>
        /// <returns>异步任务。</returns>
        internal Task SendAsync(Message msg, int bufferSize = 0)
        {
            if (this.socket == null)
            {
                throw new ObjectDisposedException("对象已经被释放。");
            }

            if (msg != null)
            {
                return msg.SendAsync(this, bufferSize);
            }

            return Task.FromResult((object)null);
        }

        /// <summary>
        /// 释放当前对象占用的资源。
        /// </summary>
        public void Dispose()
        {
            this.lastPercent = 0f;
            this.socket = null;
            this.token = CancellationToken.None;
        }
    }
}
